package com.yisin.ssh2;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Hashtable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.yisin.ssh2.jsch.ChannelShell;
import com.yisin.ssh2.jsch.JSch;
import com.yisin.ssh2.jsch.JSchException;
import com.yisin.ssh2.jsch.Session;

public class SshStream {

	private InputStream m_in;
	private OutputStream m_out;
	private ChannelShell m_channel;
	private String m_host;
	private Pattern m_prompt;
	private String m_escapeCharPattern;
	private boolean m_removeTerminalChars = false;
	private Session m_session;

	// / <summary>
	// / Constructs a new SSH stream.
	// / </summary>
	// / <param name="host">The hostname or IP address of the remote SSH
	// machine</param>
	// / <param name="username">The name of the user connecting to the remote
	// machine</param>
	// / <param name="password">The password of the user connecting to the
	// remote machine</param>
	public SshStream(String host, String username, String password) throws JSchException, IOException, InstantiationException, IllegalAccessException, ClassNotFoundException {
		this.m_host = host;
		JSch jsch = new JSch();
		m_session = jsch.getSession(username, host, 22);
		m_session.setPassword(password);

		Hashtable<Object, Object> config = new Hashtable<Object, Object>();
		config.put("StrictHostKeyChecking", "no");
		m_session.setConfig(config);

		m_session.connect();
		m_channel = (ChannelShell) m_session.openChannel("shell");

		m_in = m_channel.getInputStream();
		m_out = m_channel.getOutputStream();

		m_channel.connect();
		m_channel.setPtySize(80, 132, 1024, 768);

		setPrompt("\n");
		m_escapeCharPattern = "\\[[0-9;?]*[^0-9;]";
	}

	// / <summary>
	// / Reads a sequence of bytes from the current stream and advances the
	// position within the stream by the number of bytes read.
	// / </summary>
	// / <param name="buffer">An array of bytes. When this method returns, the
	// buffer contains the specified byte array with the values between offset
	// and (offset + count- 1) replaced by the bytes read from the current
	// source.</param>
	// / <param name="offset">The zero-based byte offset in buffer at which to
	// begin storing the data read from the current stream. </param>
	// / <param name="count">The maximum number of bytes to be read from the
	// current stream. </param>
	// / <returns>The total number of bytes read into the buffer. This can be
	// less than the number of bytes requested if that many bytes are not
	// currently available, or zero (0) if the end of the stream has been
	// reached.</returns>
	public int read(byte[] buffer, int offset, int count) throws IOException {
		return m_in.read(buffer, offset, count);
	}

	// / <summary>
	// / Reads a sequence of bytes from the current stream and advances the
	// position within the stream by the number of bytes read.
	// / </summary>
	// / <param name="buffer">An array of bytes. When this method returns, the
	// buffer contains the specified byte array with the values between offset
	// and (offset + count- 1) replaced by the bytes read from the current
	// source.</param>
	// / <returns>The total number of bytes read into the buffer. This can be
	// less than the number of bytes requested if that many bytes are not
	// currently available, or zero (0) if the end of the stream has been
	// reached.</returns>
	public int read(byte[] buffer) throws IOException {
		return read(buffer, 0, buffer.length);
	}

	// / <summary>
	// / Reads a byte from the stream and advances the position within the
	// stream by one byte, or returns -1 if at the end of the stream.
	// / </summary>
	// / <returns>The unsigned byte cast to an Int32, or -1 if at the end of the
	// stream.</returns>
	public int readByte() throws IOException {
		byte[] bytes = new byte[1];
		m_in.read(bytes);
		return bytes[0];
	}

	// / <summary>
	// / Writes a byte to the current position in the stream and advances the
	// position within the stream by one byte.
	// / </summary>
	// / <param name="value">The byte to write to the stream. </param>
	public void writeByte(byte value) throws IOException {
		m_out.write(new byte[]{ value });
	}

	// / <summary>
	// / Writes a sequence of bytes to the current stream and advances the
	// current position within this stream by the number of bytes written.
	// / </summary>
	// / <param name="buffer">An array of bytes. This method copies count bytes
	// from buffer to the current stream. </param>
	// / <param name="offset">The zero-based byte offset in buffer at which to
	// begin copying bytes to the current stream.</param>
	// / <param name="count">The number of bytes to be written to the current
	// stream. </param>
	public void write(byte[] buffer, int offset, int count) throws IOException {
		m_out.write(buffer, offset, count);
	}

	// / <summary>
	// / Writes a sequence of bytes to the current stream and advances the
	// current position within this stream by the number of bytes written.
	// / </summary>
	// / <param name="buffer">An array of bytes. This method copies count bytes
	// from buffer to the current stream. </param>
	public void write(byte[] buffer) throws IOException {
		write(buffer, 0, buffer.length);
	}

	// / <summary>
	// / Writes a String into the SSH channel. This method appends an 'end of
	// line' character to the input string.
	// / </summary>
	// / <param name="data">The String to write to the SSH channel.</param>
	public void write(String data) throws IOException {
		data += "\r";
		write(data.getBytes());
		flush();
	}

	// / <summary>
	// / Closes the current stream and releases any resources (such as sockets
	// and file handles) associated with the current stream.
	// / </summary>
	public void close() {
		try {
			m_in.close();
			m_out.close();
			m_channel.close();
			m_channel.disconnect();
			m_session.disconnect();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	// / <summary>
	// / Gets a value indicating whether the current stream supports reading.
	// / </summary>
	public boolean isCanRead() {
		return false;
	}

	// / <summary>
	// / Gets a value indicating whether the current stream supports writing.
	// / </summary>
	public boolean isCanWrite() {
		return false;
	}

	// / <summary>
	// / Gets a value indicating whether the current stream supports seeking.
	// This stream cannot seek, and will always return false.
	// / </summary>
	public boolean isCanSeek() {
		return false;
	}

	// / <summary>
	// / Clears all buffers for this stream and causes any buffered data to be
	// written to the underlying device.
	// / </summary>
	public void flush() throws IOException {
		m_out.flush();
	}

	// / <summary>
	// / Gets the length in bytes of the stream.
	// / </summary>
	public long getLength() {
		return 0;
	}

	// / <summary>
	// / Gets or sets the position within the current stream. This Stream cannot
	// seek. This property has no effect on the Stream and will always return 0.
	// / </summary>
	public long getPosition() {
		return 0;
	}

	// / <summary>
	// / This method has no effect on the Stream.
	// / </summary>
	public void setLength(long value) {
	}

	// / <summary>
	// / This method has no effect on the Stream.
	// / </summary>
	public long seek(long offset, SeekOrigin origin) {
		return 0;
	}

	// / <summary>
	// / A regular expression pattern String to match when reading the resonse
	// using the ReadResponse() method. The default prompt value is "\n" which
	// makes the ReadRespons() method return one line of response.
	// / </summary>
	public void setPrompt(String value) {
		m_prompt = Pattern.compile(value);
	}

	public String getPrompt() {
		return m_prompt.toString();
	}

	// / <summary>
	// / Gets or sets a value indicating wether this Steam sould remove the
	// terminal emulation's escape sequence characters from the response String.
	// / </summary>
	public void setRemoveTerminalEmulationCharacters(boolean value) {
		m_removeTerminalChars = value;
	}

	public boolean isRemoveTerminalEmulationCharacters() {
		return m_removeTerminalChars;
	}

	// / <summary>
	// / Gets the Cipher algorithm name used in this SSH connection.
	// / </summary>
	public String getCipher() {
		return m_session.getCipher();
	}

	// / <summary>
	// / Gets the MAC algorithm name used in this SSH connection.
	// / </summary>
	public String getMac() {
		return m_session.getMac();
	}

	// / <summary>
	// / Gets the server SSH version string.
	// / </summary>
	public String getServerVersion() {
		return m_session.getServerVersion();
	}

	// / <summary>
	// / Gets the client SSH version string.
	// / </summary>
	public String getClientVersion() {
		return m_session.getClientVersion();
	}

	// / <summary>
	// / Reads a response String from the SSH channel. This method will block
	// until the pattern in the 'Prompt' property is matched in the response.
	// / </summary>
	// / <returns>A response String from the SSH server.</returns>
	public String readResponse() throws IOException {
		int readCount;
		StringBuilder resp = new StringBuilder();
		byte[] buff = new byte[1024];
		Matcher match;
		do {
			readCount = this.read(buff);
			resp.append(new String(buff), 0, readCount);
			String s = resp.toString();
			match = m_prompt.matcher(s);
		} while (!match.find());

		return handleTerminalChars(resp.toString());
	}

	// / <summary>
	// / Removes escape sequence characters from the input string.
	// / </summary>
	// / <param name="str"></param>
	// / <returns></returns>
	private String handleTerminalChars(String str) {
		if (isRemoveTerminalEmulationCharacters()) {
			str = str.replace("(B)0", "");
			str = str.replace(m_escapeCharPattern, "");
			str = str.replace(String.valueOf((char) 15), "");
			str = str.replace(String.valueOf((char) 27) + "=*", "");
			// str = Regex.Replace(str, "\\s*\r\n", "\r\n");
		}
		return str;
	}

}
